package net.gdface.sdk;

import java.awt.Rectangle;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import net.gdface.image.ImageErrorException;
import net.gdface.image.LazyImage;
import net.gdface.image.NotImageException;
import net.gdface.image.UnsupportedFormatException;
import net.gdface.utils.Assert;
import net.gdface.utils.FaceUtilits;
import net.gdface.utils.Judge;
import net.gdface.utils.ShareLock;

/**
 * {@link FaceAPICoreAbstract}本地实现<br>
 * SDK接口类本地实现必须从此类派生<br>
 * @author guyadong
 *
 */
public abstract class BaseFaceApiLocal extends FaceApi.BaseFaceApi {
	private static final Logger logger = Logger.getLogger(BaseFaceApiLocal.class.getSimpleName());
	protected final NotFaceDetectedException NOTFACEDETECTED_INSTANCE_DEFAULT = new NotFaceDetectedException();
	protected BaseFaceApiLocal() {}
	/**
	 * 并发锁,限制建模的并发线程数,默认为CPU核数
	 */
	protected static ShareLock concurrentLock = new ShareLock(Runtime.getRuntime().availableProcessors());
	/**
	 * 建模失败图像数据的保存位置(测试研究用),为{@code null}时不保存
	 */
	private File failFolder = null;	
	/**
	 * 通过图像数据字节流(byte[])创建FaceImage对象<br>
	 * 调用{@link LazyImage#setAutoClose(boolean)}设置 autClose为{@code false}<br>
	 * 如果有特殊要求，子类可以重写此方法
	 * @param imgData
	 * @return
	 * @throws UnsupportedFormatException
	 * @throws NotImageException
	 * @see LazyImage#open()
	 * @see LazyImage#setAutoClose(boolean)
	 */
	public LazyImage makeOpenedLazyImage(byte[] imgData) throws UnsupportedFormatException, NotImageException{
		return new LazyImage(imgData).setAutoClose(false).open();
	}
	@Override
	public CodeInfo[] detectAndGetCodeInfo(byte[] imgData, int faceNum, FRect detectRectangle) throws ImageErrorException, NotFaceDetectedException {
		LazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return detectAndGetCodeInfo(lazyImg, faceNum, detectRectangle);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 对图像({@code lazyImg})中指定的人脸位置{@code facePos}并提取特征码，返回生成人脸特征码数据数组对象（人脸可能不止一个）<br>
	 * 调用该方法时假设图像({@code lazyImage})能正常解码,所以当对图像解码出现异常时，将异常对象封装到{@link RuntimeException}抛出<br>
	 * 
	 * @param lazyImg
	 *            图像对象,为{@code null}则抛出 {@link IllegalArgumentException}
	 * @param faceNum
	 * @param facePos
	 * @return 返回{@link CodeInfo}对象列表<br>
	 *         对应于{@code facePos}中的每个{@link FRect}对象,返回一个{@link CodeInfo}对象(数组下标相同):<br>
	 *         如果{@link FRect}对象为{@code null},则返回一个所有成员都为{@code null}的{@link CodeInfo}对象<br>
	 *         如果在{@link FRect}指定的范围没有提取到特征码,则{@link CodeInfo#getCode()},{@link CodeInfo#getEi()}返回{@code null}
	 * @throws NotFaceDetectedException 
	 * @see #getCodeFromImageMatrix(byte[], int, int, CodeInfo, AtomicBoolean)
	 * @see FaceApi#getCodeInfo(byte[], int, CodeInfo[])
	 */
	public List<CodeInfo> getCodeInfo(LazyImage lazyImg, int faceNum, List<CodeInfo> facePos) throws NotFaceDetectedException {
		concurrentLock.lock();
		try {
			Assert.notNull(lazyImg, "lazyImg");
			final Rectangle imgRect = lazyImg.getRectangle();
			// facePos为null或空时抛出异常
			Assert.notEmpty(facePos, "facePos");
			// 人脸位置对象数目小于要求提取的特征码的数目,则抛出异常
			if (facePos.size() < faceNum){
				throw new NotFaceDetectedException(facePos.size(), 0);
			}
			int limit = faceNum > 0 ? faceNum + 1 : Integer.MAX_VALUE;
			int faceCount = 0;
			final AtomicBoolean hasFail = new AtomicBoolean(false);
			byte[] imgData=getMatrixData(lazyImg,null);
			for (CodeInfo ci : facePos) {
				if (null != ci) {
					faceCount++;
					getCodeFromImageMatrix(imgData, imgRect.width, imgRect.height, ci, hasFail);
					if (!Judge.isEmpty(ci.getCode()) && 0 == --limit)
						break;// 提取到超过faceNum的人脸特征码,则中止循环
				}
			}
			// 提取到的人脸特征数目不等于要求提取的特征码的数目,则抛出异常
			if ((faceNum > 0)) {
				if (limit != 1){
					throw new NotFaceDetectedException(faceCount, faceNum + 1 - limit);
				}
			} else if (limit == Integer.MAX_VALUE)
				throw new NotFaceDetectedException(faceCount, 0);
			saveFail(hasFail.get(), lazyImg);
			return facePos;
		} catch (UnsupportedFormatException e) {
			throw new RuntimeException(e);
		} finally {
			concurrentLock.unlock();
		}
	}

	/**
	 * 先对图像数据{@code imgData}进行人脸检测，然后提取人脸特征码<br>
	 * 
	 * @param lazyImg
	 * @param faceNum
	 * @param detectRectangle
	 * @return
	 * @throws ImageErrorException
	 * @throws NotFaceDetectedException 
	 * @see #detectAndGetCodeInfo(byte[], int, FRect)
	 */
	public CodeInfo[] detectAndGetCodeInfo(LazyImage lazyImg, int faceNum, FRect detectRectangle) throws ImageErrorException, NotFaceDetectedException {
		List<CodeInfo> facePos = detectFace(lazyImg, detectRectangle);
		if (Judge.<List<?>> isEmpty(facePos))
			throw NOTFACEDETECTED_INSTANCE_DEFAULT;
		List<CodeInfo> codeList = getCodeInfo(lazyImg, faceNum, facePos);
		Iterator<CodeInfo> it = codeList.iterator();
		// 过滤掉没有提取到特征码的对象
		CodeInfo ci;
		while (it.hasNext()) {
			ci = it.next();
			if (null == ci.getCode())
				it.remove();
		}
		return codeList.toArray(new CodeInfo[faceNum]);
	}

	@Override
	public CodeInfo[] detectFace(byte[] imgData, FRect detectRectangle) throws ImageErrorException {
		LazyImage lazyImg = makeOpenedLazyImage(imgData);
		try {
			return detectFace(lazyImg, detectRectangle).toArray(new CodeInfo[0]);
		} finally {
			try {
				lazyImg.close();
				lazyImg = null;
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public CodeInfo[] detectFaceAgain(byte[] imgData, FRect[] detectRects) {
		Assert.notNull(detectRects, "detectRects");
		LazyImage lazyImg = null;
		try {
			lazyImg = makeOpenedLazyImage(imgData);
			CodeInfo[] res = new CodeInfo[detectRects.length];
			List<CodeInfo> list = null;
			for (int i = 0; i < detectRects.length; i++) {
				if (null != detectRects[i]) {
					list = detectFace(lazyImg, detectRects[i]);
					res[i] = list.size() == 1 ? list.get(0) : null;
				} else
					res[i] = null;
			}
			return res;
		} catch (ImageErrorException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (null != lazyImg) {
					lazyImg.close();
					lazyImg = null;
				}
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public CodeInfo[] getCodeInfo(byte[] imgData, int faceNum, CodeInfo[] facePos) throws NotFaceDetectedException {
		LazyImage lazyImg = null;
		try {
			lazyImg = makeOpenedLazyImage(imgData);
			return getCodeInfo(lazyImg, faceNum, null == facePos ? null : Arrays.asList(facePos)).toArray(
					new CodeInfo[0]);
		} catch (ImageErrorException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				if (null != lazyImg) {
					lazyImg.close();
					lazyImg = null;
				}
			} catch (IOException e) {
				throw new RuntimeException(e);
			}
		}
	}

	@Override
	public final double compareCode(byte[] code1, byte[] code2) {
		Assert.assertValidCode(code1, code2);
		return doCompareCode(code1,code2);
	}
	@Override
	public boolean isLocal() {
		return true;
	}
	/**
	 * 根据{@code hasFail}标志,保存建模失败的图像到指定的目录{@link #failFolder}
	 * 
	 * @param hasFail
	 *            为true时保存建模失败的图像数据,为{@code null}时不保存
	 * @param lazyImage
	 *            图像数据字节数组
	 * @see FaceUtilits#saveImageAutoName(byte[], File)
	 */
	protected void saveFail(boolean hasFail, LazyImage lazyImage) {
		try {
			if (hasFail && null != failFolder) {
				File failImg = lazyImage.save(failFolder);
				logger.info(String.format("保存建模失败图片 %s", failImg.getAbsolutePath()));
			}
		} catch (IOException e) {
			logger.warning(String.format("无法保存建模失败的图片 %s", e.getMessage()));
		} finally {
		}
	}

	/**
	 * @param failFolder
	 *            要设置的 failFolder
	 */
	public void setFailFolder(File failFolder) {
		this.failFolder = failFolder;
	}

	/**
	 * @return failFolder
	 */
	public File getFailFolder() {
		return failFolder;
	}
	/**
	 * 对图像({@code lazyImg})进行人脸检测，返回人脸位置数据对象{@link CodeInfo}列表<br>
	 * 
	 * @param lazyImg
	 *            图像对象,为{@code null}时抛出{@link IllegalArgumentException}
	 * @param detectRectangle
	 *            图片检测范围，为null时全图检测
	 * @return 没有检测到人脸则返回空表
	 * @throws UnsupportedFormatException
	 */
	public abstract List<CodeInfo> detectFace(LazyImage lazyImg, final FRect detectRectangle)
			throws UnsupportedFormatException;
	/**
	 * 从LazyImage对象中获取指定获取的图像矩阵数据(用于检测和建模)<br>
	 * @param lazyImg
	 * @param rect 图像区域,为null时获取全部图像
	 * @return
	 * @throws UnsupportedFormatException
	 * @see LazyImage#getMatrixRGB(Rectangle)
	 * @see LazyImage#getMatrixGray(Rectangle)
	 */
	protected abstract byte[] getMatrixData(LazyImage lazyImg,FRect rect) throws UnsupportedFormatException;
	/**
	 * 根据{@link CodeInfo}提供的人脸信息从图像矩阵中提取人脸特征码<br>
	 * 
	 * @param imgMatrix
	 *            图像矩阵
	 * @param width
	 *            图像宽度
	 * @param height
	 *            图像高度
	 * @param faceInfo
	 * 	   			人脸位置信息对象,参见 {@link FaceApi#detectFace(byte[], FRect)}
	 * @param hasFail
	 * 				测试用,可以为null,
	 * 				当需要知道是否在提取特征码的过程有失败的情况发生的时候，可以设置此参数<br>
	 * 				方法返回后如果提取特征码有失败,对象会被值为true
	 * @return 返回 {@code faceInfo} ，提取特征失败则{@link CodeInfo#getCode()}为{@code null}<br>
	 * @see FaceApi#getCodeInfo(byte[], int, CodeInfo[])
	 */
	protected abstract CodeInfo getCodeFromImageMatrix(final byte[] imgMatrix, final int width, final int height,
			CodeInfo faceInfo, final AtomicBoolean hasFail);
	
	/**
	 * 参见 {@link #compareCode(byte[], byte[])}
	 * @param code1
	 * @param code2
	 * @return
	 */
	protected abstract double doCompareCode(byte[] code1, byte[] code2) ;
}